// Copyright (c) 2000 Just Objects B.V. <just@justobjects.nl>
// Distributable under LGPL license. See terms of license at gnu.org.

package nl.justobjects.pushlet.core;

import com.alibaba.fastjson.JSON;
import nl.justobjects.pushlet.util.Sys;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Represents the event data.
 *
 * @author Just van den Broecke - Just Objects &copy;
 * @version $Id: Event.java,v 1.13 2007/11/23 14:33:07 justb Exp $
 */
@SuppressWarnings("serial")
public class Event implements Protocol, Serializable {

	protected Map<String, Serializable> attributes = new HashMap<String, Serializable>(3);
    protected String strictXMLString;
    protected String nostrictXMLString;
    protected String JSONString;
    protected boolean isUnmodifable = false;
    
    public Event() {
    	
    }

	public Event(String anEventType) {
		this(anEventType, null);
	}

    public String getJSONString() {
        return JSONString;
    }
    public String getStrictXMLString() {
        return strictXMLString;
    }
    public String getNostrictXMLString() {
        return nostrictXMLString;
    }
    public boolean isUnmodifable() {
        return isUnmodifable;
    }

    // 锁定Event，使之不可修改，保证线程安全
    public Event unmodifiableEvent() {
    	// 配置文件制定统一的eventFormat，提高处理速率
        // 如果没有指定，则会解析成所有支持的格式
    	String eventFormat = Config.getProperty(ConfigDefs.EVENT_FORMAT);
    	return unmodifiableEvent(eventFormat);
    }
	public Event unmodifiableEvent(String eventFormat) {
		if (eventFormat == null) {
			this.JSONString = this.toJSON();
			this.strictXMLString = this.toXML(true);
			this.nostrictXMLString = this.toXML();
		} else if (eventFormat.equals(FORMAT_JSON))
            this.JSONString = this.toJSON();
		else if (eventFormat.equals(FORMAT_XML_STRICT))
            this.strictXMLString = this.toXML(true);
		else if (eventFormat.equals(FORMAT_XML))
            this.nostrictXMLString = this.toXML();

        // 锁定成为只读Event
        this.attributes = Collections.unmodifiableMap(this.attributes);
        this.isUnmodifable = true;

		return this;
	}

	public Event(String anEventType, Map<String, Serializable> theAttributes) {
		if (theAttributes != null) {
			setAttrs(theAttributes);
		}
		// Set required field event type
		setField(P_EVENT, anEventType);
		// Set time in seconds since 1970
		setField(P_TIME, System.currentTimeMillis() / 1000);
	}

	public Event(Map<String, Serializable> theAttributes) {
		if (!theAttributes.containsKey(P_EVENT)) {
			throw new IllegalArgumentException(P_EVENT + " not found in attributes");
		}
		setAttrs(theAttributes);
	}

	public static Event createDataEvent(String aSubject) {
		return createDataEvent(aSubject, null);
	}

	public static Event createDataEvent(String aSubject, Map<String, Serializable> theAttributes) {
		Event dataEvent = new Event(E_DATA, theAttributes);
		dataEvent.setField(P_SUBJECT, aSubject);
		return dataEvent;
	}

	public String getEventType() {
        Serializable eventType = getField(P_EVENT);
		return (eventType == null) ? null : eventType.toString();
	}

    public void setEventType(String eventType) {
        setField(P_EVENT, eventType);
    }

	public String getSubject() {
        Serializable subject = getField(P_SUBJECT);
		return (subject == null) ? null : subject.toString();
	}

	public void setField(String name, int value) {
		attributes.put(name, Integer.valueOf(value));
	}

	public void setField(String name, long value) {
		attributes.put(name, Long.valueOf(value));
	}

    public void setField(String name, Serializable value) {
        attributes.put(name, value);
    }
	
	public void setFields(Map<String, Serializable> map) {
		attributes.putAll(map);
	}

	public Serializable getField(String name) {
        return attributes.get(name);
	}

	/**
	 * Return field; if null return default.
	 */
	public String getField(String name, String aDefault) {
        Serializable result = getField(name);
		return (result == null) ? aDefault : result.toString();
	}

	public Iterator<String> getFieldNames() {
		return attributes.keySet().iterator();
	}

	public String toString() {
		return attributes.toString();
	}

	/**
	 * Convert to HTTP query string.
	 */
	public String toQueryString() {
		StringBuilder queryString = new StringBuilder(128);
		String amp = "";
		for (Iterator<Map.Entry<String, Serializable>> entryIterator = attributes.entrySet().iterator(); entryIterator.hasNext();) {
			Map.Entry<String, Serializable> nextAttr = entryIterator.next();
			String nextAttrName = nextAttr.getKey();
			Serializable nextAttrValue = nextAttr.getValue();
			if (nextAttrValue == null)
				continue;
			queryString.append(amp).append(nextAttrName).append("=").append(nextAttrValue.toString());
			amp = "&";
		}
		return queryString.toString();
	}

	public String toXML(boolean strict) {
		int size = attributes.size();
		Iterator<Map.Entry<String, Serializable>> entryIterator = attributes.entrySet().iterator();
		StringBuilder xmlString = new StringBuilder(128).append("<event ");
		while (size > 0) {
			Map.Entry<String, Serializable> nextAttr = entryIterator.next();
			String nextAttrName = nextAttr.getKey();
			Serializable nextAttrValue = nextAttr.getValue();
			if (nextAttrValue == null)
				continue;
			xmlString.append(nextAttrName).append("=\"").append((strict ? Sys.forHTMLTag(nextAttrValue.toString()) : nextAttrValue)).append("\" ");
			--size;
		}
		xmlString.append("/>");
		return xmlString.toString();
	}

    public String toJSON() {
        return JSON.toJSONString(attributes);
    }

	public String toXML() {
		return toXML(false);
	}

	public Object clone() {
		// Clone the Event by using copy constructor
		return new Event(attributes);
	}

	/**
	 * Copy given attributes into event attributes
	 */
	private void setAttrs(Map<String, Serializable> theAttributes) {
		attributes.putAll(theAttributes);
	}
	
	protected String appendToJSON() {
        Iterator<Map.Entry<String, Serializable>> entryIterator = attributes.entrySet().iterator();
        int size = attributes.size();
		StringBuilder sb = new StringBuilder(20 * size);
		sb.append("{");
        while (size > 0) {
        	Map.Entry<String, Serializable> entry = entryIterator.next();
        	sb.append("\"").append(entry.getKey()).append("\":");
        	Serializable value = entry.getValue();
        	if (value instanceof String)
        		sb.append("\"").append(value).append("\"");
        	else if ((value instanceof Long) || (value instanceof Integer))
        		sb.append(value);
        	if (size > 1)
        		sb.append(",");
            --size;
        }
        sb.append("}");
        return sb.toString();
	}
	
	public Map<String, Serializable> getAttributes() {
		return this.attributes;
	}
}

/*
  * $Log: Event.java,v $
  * Revision 1.13  2007/11/23 14:33:07  justb
  * core classes now configurable through factory
  *
  * Revision 1.12  2006/05/15 11:52:53  justb
  * updates mainly for AJAX client
  *
  * Revision 1.11  2006/05/06 00:06:28  justb
  * first rough version AJAX client
  *
  * Revision 1.10  2005/02/21 11:50:46  justb
  * ohase1 of refactoring Subscriber into Session/Controller/Subscriber
  *
  * Revision 1.9  2005/02/20 13:05:32  justb
  * removed the Postlet (integrated in Pushlet protocol)
  *
  * Revision 1.8  2005/02/15 13:29:24  justb
  * add toQueryString()
  *
  * Revision 1.7  2005/01/18 16:47:10  justb
  * protocol changes for v2 and publishing from pushlet client
  *
  * Revision 1.6  2005/01/13 14:47:15  justb
  * control evt: send response on same (control) connection
  *
  * Revision 1.5  2004/09/03 22:35:37  justb
  * Almost complete rewrite, just checking in now
  *
  * Revision 1.4  2004/08/15 16:00:15  justb
  * enhancements to pull mode
  *
  * Revision 1.3  2003/08/15 08:37:40  justb
  * fix/add Copyright+LGPL file headers and footers
  *
  * Revision 1.2  2003/05/18 16:15:08  justb
  * support for XML encoded Events
  *
  * Revision 1.1.1.1  2002/09/24 21:02:30  justb
  * import to sourceforge
  *
  */
